; MIDI PORT ROUTINES
; copyright Kirk Austin 1985
; Transmit and Receive queue length equates
TxQSize EQU $100
RxQSize EQU $100
; Serial Chip Addresses, offsets, and system equates
sccRBase EQU $9FFFF8
sccWBase EQU $BFFFF9
Lvl2DT EQU $1B2
aData EQU 6
aCtl EQU 2
bData EQU 4
bCtl EQU 0
TBE EQU 2
CurrentA5 EQU $904
; This is an example of how to use the routines.
; If this were placed in your event loop your application would
; receive MIDI data and echo it back out.
;
;MIDIThru
; CLR -(SP) ; clear space for result
; BSR RxMIDI ; fetch data
; MOVE (SP)+,D0
; CMPI #$FFFF,D0 ; any bytes available?
; BEQ NoMIDI ; if not, exit
; MOVE D0,-(SP) ; if so, transmit them
; BSR TxMIDI
; BRA MIDIThru ; check for more
;NoMIDI
;
;
---------------------------------------------------------------------h
---
; This section contains the necessary routines for MIDI
;
; These are the initialization routines which should be called
; when you initialize quickdraw and the various managers.
; Call SCCInitA to use the modem port, and SCCInitB to use
; the printer port.
SCCInitA
MOVE #aCtl,CtlOffset(A5) ; set up globals for Chn A
MOVE #aData,DataOffset(A5)
MOVE.B #%10000000,ChnReset(A5)
MOVE #24,RxIntOffset(A5)
MOVE #16,TxIntOffset(A5)
MOVE #28,SpecRecCond(A5)
BRA SCCInit
SCCInitB
MOVE #bCtl,CtlOffset(A5) ; set up globals for Chn B
MOVE #bData,DataOffset(A5)
MOVE.B #%01000000,ChnReset(A5)
MOVE #8,RxIntOffset(A5)
MOVE #0,TxIntOffset(A5)
MOVE #12,SpecRecCond(A5)
SCCInit
MOVE SR,-(SP) ; Save interrupts
MOVEM.L D0/A0-A1,-(SP) ; Save registers
ORI #$0300,SR ; Disable interrupts
MOVE.L #sccRBase,A1 ; Get base Read address
ADD CtlOffset(A5),A1 ; Add offset for control
MOVE.B (A1),D0 ; Dummy read
MOVE.L (SP),(SP) ; Delay
MOVE.L #sccWBase,A0 ; Get base Write address
ADD CtlOffset(A5),A0 ; Add offset for control
MOVE.B #9,(A0) ; pointer for SCC reg 9
MOVE.L (SP),(SP) ; Delay
MOVE.B ChnReset(A5),(A0) ; Reset channel
MOVE.L (SP),(SP) ; Delay
MOVE.B #4,(A0) ; pointer for SCC reg 4
MOVE.L (SP),(SP) ; Delay
; This is where you determine the external clock rate
; %01000100 = 500K
; %10000100 = 1 Meg
; %11000100 = 2 Meg
MOVE.B #%01000100,(A0) ; 16x clock, 1 stop bit
MOVE.L (SP),(SP) ; Delay
MOVE.B #1,(A0) ; pointer for SCC reg 1
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00000000,(A0) ; No W/Req
MOVE.L (SP),(SP) ; Delay
MOVE.B #3,(A0) ; pointer for SCC reg 3
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00000000,(A0) ; Turn off Rx
MOVE.L (SP),(SP) ; Delay
MOVE.B #5,(A0) ; pointer for SCC reg 5
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00000000,(A0) ; Turn off Tx
MOVE.L (SP),(SP) ; Delay
MOVE.B #11,(A0) ; pointer for SCC reg 11
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00101000,(A0) ; Make TRxC clock sourc
MOVE.L (SP),(SP) ; Delay
MOVE.B #14,(A0) ; pointer for SCC reg 14
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00000000,(A0) ; Disable BRGen
MOVE.L (SP),(SP) ; Delay
MOVE.B #3,(A0) ; pointer for SCC reg 3
MOVE.L (SP),(SP) ; Delay
MOVE.B #%11000001,(A0) ; Enable Rx
MOVE.L (SP),(SP) ; Delay
MOVE.B #5,(A0) ; pointer for SCC reg 5
MOVE.L (SP),(SP) ; Delay
MOVE.B #%01101010,(A0) ; Enable Tx and drivers
MOVE.L (SP),(SP) ; Delay
MOVE.B #15,(A0) ; pointer for SCC reg 15
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00001000,(A0) ; Enable DCD int for
; mouse
MOVE.L (SP),(SP) ; Delay
MOVE.B #0,(A0) ; pointer for SCC reg 0
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00010000,(A0) ; Reset EXT/STATUS
MOVE.L (SP),(SP) ; Delay
MOVE.B #0,(A0) ; pointer for SCC reg 0
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00010000,(A0) ; Reset EXT/STATUS
MOVE.L (SP),(SP) ; Delay
MOVE.B #1,(A0) ; pointer for SCC reg 1
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00010011,(A0) ; Enable interrupts
MOVE.L (SP),(SP) ; Delay
MOVE.B #9,(A0) ; pointer for SCC reg 9
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00001010,(A0) ; Set master int enable
MOVE.L (SP),(SP) ; Delay
MOVE.L #Lvl2DT,A0 ; get dispatch table
; pointer
MOVE RxIntOffset(A5),D0 ; get offset to Rx vector
LEA RxIntHand,A1 ; set Rx vector
MOVE.L A1,0(A0,D0)
MOVE TxIntOffset(A5),D0 ; get offset to Tx vector
LEA TxIntHand,A1 ; set Tx vector
MOVE.L A1,0(A0,D0)
MOVE SpecRecCond(A5),D0 ; get offset to
; Special vector
LEA Stub,A1
MOVE.L A1,0(A0,D0)
CLR RxByteIn(A5) ; init flags & pointers
CLR RxByteOut(A5)
MOVE.B #$FF,RxQEmpty(A5)
CLR TxByteIn(A5)
CLR TxByteOut(A5)
MOVE.B #$FF,TxQEmpty(A5)
MOVEM.L (SP)+,D0/A0-A1 ; Restore registers
MOVE (SP)+,SR ; Restore interrupts
RTS ; and return
; This is the routine to transmit a MIDI byte of data. To use this
; place the byte to be transmitted as the lower 8 bits of a word
; routine on the stack, then BSR to TxMIDI.
TxMIDI
LINK A6,#0 ; set frame pointer
MOVE SR,-(SP) ; Save interrupts
MOVEM.L D0/A0-A2,-(SP) ; Save registers
ORI #$0300,SR ; Disable interrupts
TST.B TxQEmpty(A5) ; is TxQueue empty?
BNE TxQE ; if so branch
MOVE TxByteIn(A5),D0 ; if not add byte to queue
LEA TxQueue(A5),A2 ; point to queue
MOVE.B 9(A6),0(A2,D0) ; place byte in queue
ADDQ #1,D0 ; update TxByteIn
CMP #TxQSize,D0
BNE @1
MOVE #0,D0
@1 MOVE D0,TxByteIn(A5)
BRA TxExit ; and exit
TxQE
MOVE.L #sccRbase,A0 ; get SCC Read Address
MOVE.L #sccWbase,A1 ; get SCC Write address
MOVE CtlOffset(A5),D0 ; get index for Ctl
BTST.B #TBE,0(A0,D0) ; transmit buffer empty?
BNE FirstByte ; if so branch
MOVE TxByteIn(A5),D0 ; if not add to queue
LEA TxQueue(A5),A2 ; point to queue
MOVE.B 9(A6),0(A2,D0) ; place byte in queue
ADDQ #1,D0 ; update index
CMP #TxQSize,D0
BNE @1
MOVE #0,D0
@1 MOVE D0,TxByteIn(A5)
MOVE.B #0,TxQEmpty(A5) ; reset queue empty flag
BRA TxExit ; and exit
FirstByte
MOVE DataOffset(A5),D0 ; get index to data
MOVE.L (SP),(SP) ; delay
MOVE.B 9(A6),0(A1,D0) ; write data to SCC
MOVE.L (SP),(SP) ; Delay
TxExit
MOVEM.L (SP)+,D0/A0-A2 ; Restore registers
MOVE (SP)+,SR ; Restore interrupts
UNLK A6 ; release frame pointer
MOVE.L (SP)+,A1 ; save return address
ADD.L #2,SP ; move past data word
MOVE.L A1,-(SP) ; put address back on stack
RTS ; and return
; This is the routine to receive a byte of MIDI data. To use this
; routine treat it like a Pascal function. Leave space on the
; stack for a word of data before BSR'ing to this routine. If the
; routine executes is $FFFF there was no MIDI data available.
; If the upper byte is clear then a valid MIDI byte is in the lower
; 8 bits.
RxMIDI
LINK A6,#0 ; set frame pointer
MOVE SR,-(SP) ; Save interrupts
MOVEM.L D0-D1/A0-A2,-(SP) ; Save registers
ORI #$0300,SR ; disable interrupts
TST.B RxQEmpty(A5) ; any data available?
BEQ @1 ; if so, branch
MOVE #$FFFF,8(A6) ; if not, return with $FFFF
BRA RxExit
@1 MOVE RxByteOut(A5),D0 ; get index to byte out
LEA RxQueue(A5),A2 ; point to queue
MOVE.L #0,D1 ; clear data register
MOVE.B 0(A2,D0),D1 ; get MIDI data
MOVE D1,8(A6) ; place it on stack for return
ADDQ #1,D0 ; update index
CMP #RxQSize,D0
BNE @2
MOVE #0,D0
@2 MOVE D0,RxByteOut(A5)
MOVE RxByteIn(A5),D1
CMP D0,D1 ; is queue empty?
BNE RxExit ; if not exit
MOVE.B #$FF,RxQEmpty(A5) ; if empty, set flag
RxExit
MOVEM.L (SP)+,D0-D1/A0-A2 ; Restore registers
MOVE (SP)+,SR ; restore interrupts
UNLK A6
RTS ; and return
; This is the interrupt routine for receiving a byte of MIDI data.
; It places the received byte in a circular queue to be
; accessed later by the application.
RxIntHand
ORI #$0300,SR ; disable interrupts
MOVEM.L D0-D1/A0-A2/A5,-(SP) ; save registers
MOVE.L CurrentA5,A5 ; make sure A5 is correct
MOVE.L #sccRBase,A0 ; get SCC address
MOVE.L #sccWBase,A1
MOVE DataOffset(A5),D0 ; get data offset
MOVE.B 0(A0,D0),D1 ; read data from SCC
MOVE.L (SP),(SP) ; Delay
LEA RxQueue(A5),A2 ; point to queue
MOVE RxByteIn(A5),D0 ; get offset to next cell
MOVE.B D1,0(A2,D0) ; put byte in queue
MOVE.B #0,RxQEmpty(A5) ; reset queue empty flag
ADDQ #1,D0 ; update index
CMP #RxQSize,D0
BNE @1
MOVE #0,D0
@1 MOVE D0,RxByteIn(A5)
MOVEM.L (SP)+,D0-D1/A0-A2/A5 ; restore registers
ANDI #$F8FF,SR ; enable interrupts
RTS ; and return
; This is the interrupt routine for transmitting a byte of MIDI
; data. It checks to see if there is any data to send. If there is
; it sends it to the SCC. If there isn't it resets the TBE interrupt
; in the SCC and exits.
TxIntHand
ORI #$0300,SR ; disable interrupts
MOVEM.L D0-D1/A0-A2/A5,-(SP) ; save registers
MOVE.L CurrentA5,A5
MOVE.L #sccRBase,A0 ; get SCC address
MOVE.L #sccWBase,A1
TST.B TxQEmpty(A5) ; Is queue empty?
BEQ @1 ; if not branch
MOVE CtlOffset(A5),D0 ; get offset for control
MOVE.B #$28,0(A1,D0) ; if so, reset TBE
; interrupt
MOVE.L (SP),(SP) ; Delay
BRA TxIExit ; and exit
@1 MOVE TxByteOut(A5),D0 ; get index to next data
; byte
LEA TxQueue(A5),A2 ; point to queue
MOVE DataOffset(A5),D1 ; get data offset
MOVE.B 0(A2,D0),0(A1,D1) ; write data to SCC
MOVE.L (SP),(SP) ; Delay
ADDQ #1,D0 ; update index
CMP #TxQSize,D0
BNE @2
MOVE #0,D0
@2 MOVE D0,TxByteOut(A5)
MOVE TxByteIn(A5),D1
CMP D0,D1 ; is TxQueue empty?
BNE TxIExit ; if not exit
MOVE.B #$FF,TxQEmpty(A5) ; if empty set flag
TxIExit
MOVEM.L (SP)+,D0-D1/A0-A2/A5 ; restore registers
ANDI #$F8FF,SR ; enable interrupts
RTS ; and return
; This routine must be called when the application quits or the
; system will crash due to the interrupt handling pointers
; becoming invalid.
SCCResetA
MOVE #aCtl,CtlOffset(A5) ; set up globals for Chn A
MOVE #aData,DataOffset(A5)
MOVE.B #%10000000,ChnReset(A5)
MOVE #24,RxIntOffset(A5)
MOVE #16,TxIntOffset(A5)
MOVE #28,SpecRecCond(A5)
BRA SCCReset
SCCResetB
MOVE #bCtl,CtlOffset(A5) ; set up globals for Chn B
MOVE #bData,DataOffset(A5)
MOVE.B #%01000000,ChnReset(A5)
MOVE #8,RxIntOffset(A5)
MOVE #0,TxIntOffset(A5)
MOVE #12,SpecRecCond(A5)
SCCReset
MOVE SR,-(SP) ; Save interrupts
MOVE.L A0,-(SP) ; Save register
ORI #$0300,SR ; Disable interrupts
MOVE.L #sccWBase,A0 ; Get base Write address
ADD CtlOffset(A5),A0 ; Add offset for control
MOVE.B #9,(A0) ; pointer for SCC reg 9
MOVE.L (SP),(SP) ; Delay
MOVE.B ChnReset(A5),(A0) ; Reset channel
MOVE.L (SP),(SP) ; Delay
MOVE.B #15,(A0) ; pointer for SCC reg 15
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00001000,(A0) ; Enable DCD int
MOVE.L (SP),(SP) ; Delay
MOVE.B #0,(A0) ; pointer for SCC reg 0
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00010000,(A0) ; Reset EXT/STATUS
MOVE.L (SP),(SP) ; Delay
MOVE.B #0,(A0) ; pointer for SCC reg 0
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00010000,(A0) ; Reset EXT/STATUS
MOVE.L (SP),(SP) ; Delay
MOVE.B #1,(A0) ; pointer for SCC reg 1
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00000001,(A0) ; Enable mouse
; interrupts
MOVE.L (SP),(SP) ; Delay
MOVE.B #9,(A0) ; pointer for SCC reg 9
MOVE.L (SP),(SP) ; Delay
MOVE.B #%00001010,(A0) ; Set master int enable
MOVE.L (SP),(SP) ; Delay
MOVE.L (SP)+,A0 ; Restore register
MOVE (SP)+,SR ; Restore interrupts
RTS ; and return
; this is the space for a special condition interrupt routine
Stub
RTS
;-------------------------------MIDI
Globals--------------------------------
CtlOffset DS.W 1 ; offset for channel control
DataOffset DS.W 1 ; offset for channel data
ChnReset DS.B 1 ; SCC channel reset select
RxIntOffset DS.W 1 ; offset for dispatch table
TxIntOffset DS.W 1 ; offset for dispatch table
SpecRecCond DS.W 1 ; offset for dispatch table
TxQueue DS.B TxQSize ; transmitted data queue
TxQEmpty DS.B 1 ; Transmit queue empty flag
TxByteIn DS.W 1 ; index to next cell in
TxByteOut DS.W 1 ; index to next cell out
RxQueue DS.B RxQSize ; received data queue
RxQEmpty DS.B 1 ; receive queue empty flag
RxByteIn DS.W 1 ; index to next cell in
RxByteOut DS.W 1 ; index to next cell out
End